﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;


namespace Chess_Model
{
    //class Program
    //{
    //    static void Main(string[] args)
    //    {

    //        ChessBoard board = new ChessBoard();
    //        board.printBoard();


    //        board.move(new Position(4, 6), new Position(4, 4));
    //        board.printBoard();

    //        board.move(new Position(4, 4), new Position(4, 3));
    //        board.printBoard();

    //    }
    //}


    //
    // Parse and execute commands either from standard input or from a file.
    //
    public class CommandHandler
    {

        // Instance variable to interface directly with ChessBoard.
        // We can have one instance per CommandHandler, so if we
        //  want to reset a game, we create a new chessboard.
        private ChessBoard board;


        public CommandHandler(ChessBoard b)
        {
            if (b == null)
                throw new ArgumentException();
            board = b;
        }


        // Display the default prompt for command input
        public static void promptForCommand()
        {
            Console.Write("chess-> ");
        }

        // Display the given prompt for command input
        public static void promptForCommand(string prompt)
        {
            Console.Write(prompt);
        }

        //---------------------------------------------------------------------
        // Read one of the commands from the given input
        // Return an instance of Command class or null if command is invalid.
        //---------------------------------------------------------------------
        public static Command getCommand(TextReader input)
        {
            string line = null;
            Command command = null;

            try
            {
                line = input.ReadLine();
                command = new Command(line);
            }
            catch (ArgumentException)
            {
                // Must be from Command constructor in case of invalid command
                return null;
            }
            catch (Exception e)
            {
                // Some IO error
                Console.WriteLine("---Error in getCommand()---\n{0}", e.Message);
                Console.WriteLine("---StackTrace---\n{0}", e.StackTrace);
            }

            return command;
        }

        //---------------------------------------------------------------------
        // Carry out requested command by directly calling ChessBoard methods.
        // Return command code to handle any special command such as 'exit'. 
        // See Command class for a list of command codes.
        // Ideally, all error/info messages should be printed out here since
        //  this class is the user interface between the program and user. 
        //
        // XXX: Modifed to be instance method because we need an instance of
        //      ChessBoard class to manipulate, which is also made an instance
        //      variable.
        //---------------------------------------------------------------------
        public int executeCommand(Command cmd)
        {
            if (cmd == null)
            {
                Console.WriteLine("Invalid command");
                return Command.COMMAND_INVALID;
            }

            int code = cmd.getCommandCode();
            switch (code)
            {
                case Command.COMMAND_INVALID:
                    // Based on the convention of getCommand()
                    // invalid command is presented by 'null'.
                    // Added this code for completion.
                    Console.WriteLine("Invalid command");
                    break;
                case Command.COMMAND_HELP:
                    Command.printSupportedCommands();
                    break;
                case Command.COMMAND_EXIT:      // Return to main since we
                    break;                      // need to clean up resources.
                case Command.COMMAND_MOVE:
                    int[] args   = cmd.getArguments();
                    bool success = board.move(args[0], args[1], args[2], args[3]);
                    if (!success)
                        Console.WriteLine("Illegal move");
                    break;
                case Command.COMMAND_PRINT:
                    board.printBoard();
                    break;
                case Command.COMMAND_RESET:
                    board = new ChessBoard();
                    Console.WriteLine("New game is started");
                    break;
                default:
                    Console.WriteLine("Either umimplemented command or a bug.");
                    break;
            }

            return code;
        }


        //---------------------------------------------------------------------
        // Read supported commands and carry out appropriate tasks.
        // This supports reading and writing to files.
        //
        // If no argument is given, it will read from stdin and write to stdout
        // If one argument is given, it will read from the file
        // If two arguments are given, it will read from the first file and 
        //  write to the second file.
        //
        // Exception will be thrown in case of IO error and program will exit.
        //---------------------------------------------------------------------
        public static void Main(string[] args)
        {
            TextReader input = null;
            TextWriter output = null;
            // Set up IO redirection
            try
            {
                input = Console.In;
                output = Console.Out;
                if (args.Length >= 1)
                    input = new StreamReader(args[0]);
                if (args.Length >= 2)
                    output = new StreamWriter(args[1]);
                Console.SetIn(input);
                Console.SetOut(output);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine("File(s) cannot be accessed.\n{0}", ex.Message);
                Environment.Exit(1);
            }

            ChessBoard b = new ChessBoard();
            CommandHandler cmdHandler = new CommandHandler(b);
            Command cmd = null;
            int cmdCode = Command.COMMAND_RESET; // new game (same effect as reset)

            while (cmdCode != Command.COMMAND_EXIT)
            {
                promptForCommand();
                cmd     = getCommand(input);
                cmdCode = cmdHandler.executeCommand(cmd);
            }

            input.Close();
            output.Close();
        }


        //
        // Class to represent a command to carry out.
        // Made a nested class since only CommandHandler uses this class.
        //
        public class Command
        {

            // Constants for some special commands to handle in the read-eval loop
            // We can use 'enum' here too, but we have to cast it to 'int' later...
            // Outside class should use these constants to find out what command
            //  was given.
            // special flag
            public const int COMMAND_INVALID = -1;   
            // command codes
            public const int COMMAND_HELP = 0;
            public const int COMMAND_EXIT = 1;      // exit requested
            public const int COMMAND_RESET = 2;     // just start out or reset
            public const int COMMAND_PRINT = 3;
            public const int COMMAND_MOVE = 4;

            // Mapping of:
            //  - string command 
            //  - array of its legal argument count (index 0) and command code (index 1)
            //
            // XXX: Changed modifer to 'private' because outside class should not
            //      access it directly.
            // Note:
            //  - This mapping is one-to-one with the command constants above.
            private static Dictionary<string, int[]> commandList =
                new Dictionary<string, int[]>()
            {
                {"help",  new int[] {0, COMMAND_HELP }}, 
                {"exit",  new int[] {0, COMMAND_EXIT }},  // 'exit'
                {"reset", new int[] {0, COMMAND_RESET }}, // 'reset'
                {"print", new int[] {0, COMMAND_PRINT }}, // 'print'
                {"move",  new int[] {4, COMMAND_MOVE }},  // 'move x1 y1 x2 y2'
            };

            // See constructor comments regarding the purposes and possible values.
            private string command;
            private int commandCode;
            // XXX: It seems like we only need int arguments to our commands
            //      so far, so I changed 'arguments' to int[].
            private int[] arguments;


            //-----------------------------------------------------------------
            // Ugly code for constructor until I find a clean way.
            // Throw ArgumentException if the argument is null or represents an
            //  invalid/unsupported command or any of the arguments does not
            //  represent a valid 32-bit integer.
            //
            // Note:
            //  - 'command' is a command string, such as 'move' or 'exit'...
            //  - 'arguments' stores the integer arguments of the command. It is
            //      null if the command does not accept any arguments.
            //  - 'commandCode' corresponds to the command constants defined
            //      in this class. Outside class should use these constants
            //      to check and carry out appropriate operations for the 
            //      commands. Hence, it should not manipulate directly with
            //      'commandList'.
            //-----------------------------------------------------------------
            public Command(string line)
            {
                if (line == null)
                    throw new ArgumentException();

                // Parse the argument into command and arguments (if any).
                string[] words = line.Split(new char[] { ' ' });
                if (words == null || words.Length < 1)
                    throw new ArgumentException();

                command = words[0];
                arguments = null;

                // If given command has arguments, convert all but the first word.
                if (words.Length > 1)
                {
                    arguments = new int[words.Length - 1];
                    for (int i = 0; i < words.Length - 1; i++)
                    {
                        try
                        {
                            arguments[i] = Convert.ToInt32(words[i + 1]);
                        }
                        catch (Exception)   // Invalid format or overflow
                        {
                            throw new ArgumentException();
                        }
                    }
                }

                if (!isCommandValid(command, arguments))
                    throw new ArgumentException();

                commandCode = commandList[command][1];
            }


            public int getCommandCode()
            {
                return commandCode;
            }


            public int[] getArguments()
            {
                return arguments; 
            }


            // Print out supported commands.
            public static void printSupportedCommands()
            {
                Console.WriteLine("{0}\t{1}\t{2}\t{3}", 
                                  "Command", "Arg.", 
                                  "Purpose                    ", 
                                  "Syntax");
                Console.WriteLine(
                    "----------------------------------------------------------------");

                Console.WriteLine("{0}\t{1}\t{2}\t{3}",
                                  "help", 0, 
                                  "Print this message         ", "help");
                
                Console.WriteLine("{0}\t{1}\t{2}\t{3}",
                                 "exit", 0, 
                                 "Exit program                ", "exit");
                
                Console.WriteLine("{0}\t{1}\t{2}\t{3}",
                                 "reset", 0, 
                                 "Start a new game            ", "reset");
                
                Console.WriteLine("{0}\t{1}\t{2}\t{3}",
                                 "print", 0, 
                                 "Print out the chess board   ", "print");
                
                Console.WriteLine("{0}\t{1}\t{2}\t{3}",
                                 "move", 4, 
                                 "Move from (x1,y1) to (x2,y2)", 
                                 "move x1 y1 x2 y2");
            }


            //-----------------------------------------------------------------
            // Return true if the command is supported/valid.
            // Return false on unmatched argument count or on invalid command.
            // 
            // If a command has no arguments, 'a' must be null.
            // A command is valid if it is one of the supported command AND
            //  the number of arguments matches the command's expected 
            //  argument count.
            //-----------------------------------------------------------------
            public static bool isCommandValid(string c, int[] a)
            {
                if (c == null) return false;

                // commandList is a dictionary. We check if the given command is
                //  in there (valid) by directly fetching it from 'commandList'.
                //  KeyNotFoundException will be thrown if the given command is
                //  not in there (invalid). If the command is valid, we then  
                //  fetch the expected argument count of the command and check
                //  correspondingly.
                try
                {
                    int expectedCount = Command.commandList[c][0];

                    if ((a == null && expectedCount > 0) ||
                        (a != null && expectedCount == 0) ||
                        (a != null && expectedCount != a.Length))
                        return false;
                }
                catch (KeyNotFoundException)    // Invalid command given
                {
                    return false;
                }

                return true;
            }


            public override string ToString()
            {
                StringBuilder result = new StringBuilder(command);
                if (arguments != null)
                    foreach (int i in arguments)
                        result.Append(" " + i);
                return result.ToString();
            }

        }   // Command class
    }   // CommandHandler class
}